package cn.zf233.springsecurityjwt.config;

import cn.zf233.springsecurityjwt.util.JwtUtil;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Created by zf233 on 2021/7/12
 */
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private static final String AUTHORIZATION_TOKEN = "Authorization";
    private static final String BEARER_STR = "Bearer ";
    private final RequestHeaderRequestMatcher requiresAuthenticationRequestMatcher;

    @Resource
    private JwtDetailsService jwtDetailsService;

    @Resource
    private JwtUtil jwtUtil;

    public JwtAuthenticationTokenFilter() {
        this.requiresAuthenticationRequestMatcher = new RequestHeaderRequestMatcher("Authorization");
    }

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest httpServletRequest, @NonNull HttpServletResponse httpServletResponse, @NonNull FilterChain filterChain) throws ServletException, IOException {
        // 请求头存在 token，这里可以做延期等操作
        if (requiresAuthentication(httpServletRequest)) {
            String token = httpServletRequest.getHeader(AUTHORIZATION_TOKEN).replace(BEARER_STR, "");
            String msg;
            Long userId = null;
            if (StringUtils.isBlank(token)) {
                msg = "未携带用户凭证";
            } else {
                try {
                    jwtUtil.verify(token);
                    userId = jwtUtil.getUserIdByToken(token);
                    UserDetails user = jwtDetailsService.loadUserByUsername(String.valueOf(userId));
                    if (ObjectUtils.isEmpty(user)) {
                        throw new BadCredentialsException("用户信息加载失败");
                    }
                    msg = "用户凭证验证成功";
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                } catch (TokenExpiredException e) {
                    msg = "凭证已经过期";
                } catch (SignatureVerificationException e) {
                    msg = "凭证签名错误";
                } catch (AlgorithmMismatchException e) {
                    msg = "凭证加密算法不匹配";
                } catch (Exception e) {
                    msg = "无效的凭证";
                }
            }
            log.info("验证结果 - Msg:{} 、userId:{}", msg, userId);
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }

    protected boolean requiresAuthentication(HttpServletRequest request) {
        return requiresAuthenticationRequestMatcher.matches(request);
    }
}
